package communication;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.Vector;

import javax.bluetooth.BluetoothStateException;
import javax.bluetooth.DataElement;
import javax.bluetooth.DeviceClass;
import javax.bluetooth.DiscoveryAgent;
import javax.bluetooth.DiscoveryListener;
import javax.bluetooth.LocalDevice;
import javax.bluetooth.RemoteDevice;
import javax.bluetooth.ServiceRecord;
import javax.bluetooth.UUID;
import javax.microedition.io.Connector;
import javax.microedition.io.StreamConnection;
import javax.microedition.io.StreamConnectionNotifier;

import model.MsgContact;
import base.Comm;
import base.MessageListener;
import exceptions.InitializedException;

public final class BluetoothComm implements Comm, DiscoveryListener {

	// services addresses
	private String serviceName;
	private String btAddress;
	private int image;
	private static final UUID[] SERVICE_UUID = { new UUID(
			"09876543210987654321ABCDEFFEDCBA", false) };
	private final StringBuffer serviceUrl;
	private final int SERVICE_ATTR_ID = 0x1234;
	private final int SERVICE_ATTR_IMG = 0x5678;
	private final int[] SERVICE_ATTR_SET = { SERVICE_ATTR_ID, SERVICE_ATTR_IMG };
	//
	private LocalDevice localDevice;
	//
	private Sender send;
	private Receiver receive;
	private Thread receiver;
	private Thread sender;
	private boolean receiverState; // true == started;
	private volatile boolean stop;
	//
	private Vector remoteDevices;
	private int transactions;
	// thread variables
	private StreamConnection connection;
	private DataOutputStream output;
	private DataInputStream input;
	private StreamConnectionNotifier notifier;
	private ServiceRecord record;
	// auxiliary variables
	private volatile int remoteDevicesSize;
	private MsgContact[] contactsElement;
	private int contactsElementSize;

	// state variable

	public BluetoothComm() {
		stop = false;
		receiverState = false;
		serviceUrl = new StringBuffer(128);
	}

	/* IComm Methods */
	public void init(String service, int image) throws InitializedException {
		if (localDevice != null)
			throw new InitializedException();
		try {
			localDevice = LocalDevice.getLocalDevice();
			localDevice.setDiscoverable(DiscoveryAgent.GIAC);
		} catch (BluetoothStateException e) {}
		serviceName = service;
		this.image = image;
		serviceUrl.append("btspp://localhost:");
		serviceUrl.append(SERVICE_UUID[0]);
		serviceUrl.append(";name=");
		serviceUrl.append(serviceName);
		serviceUrl.append(";authorize=false");
	}

	public boolean send(String url, String msg) {
		if (send == null) {
			send = new Sender();
		}
		send.set(url, msg);
		sender = new Thread(send);
		sender.start();
		// clean up
		sender = null;
		return true;
	}
	
	public MsgContact[] search() {
		remoteDevices = new Vector();
		DiscoveryAgent discoveryAgent = localDevice.getDiscoveryAgent();
		try {
			discoveryAgent.startInquiry(DiscoveryAgent.GIAC, this);
			synchronized (this) {
				this.wait();
			}
		} catch (InterruptedException e) {
			return null;
		} catch (BluetoothStateException e) {
			return null;
		}
		remoteDevicesSize = remoteDevices.size();
		if (remoteDevicesSize == 0)
			return null;
		contactsElement = new MsgContact[remoteDevicesSize];
		contactsElementSize = 0;
		try {
			for (int i = 0; i < remoteDevicesSize; i++)
				discoveryAgent.searchServices(SERVICE_ATTR_SET, SERVICE_UUID,
						(RemoteDevice) remoteDevices.elementAt(i), this);
			synchronized (this) {
				this.wait();
			}
		} catch (InterruptedException e) {
			return null;
		} catch (BluetoothStateException e) {
			return null;
		}
		if (contactsElement == null)
			return null;
		// cleanup
		remoteDevices = null;
		discoveryAgent = null;
		return contactsElement;
	}

	public boolean startReceiver(MessageListener listener) {
		if (receiverState)
			return false;
		if (receive == null) {
			receive = new Receiver();
		}
		receive.set(listener);
		receiver = new Thread(receive);
		receiver.start();
		return true;
	}

	public boolean stopReceiver() {
		if (!receiverState)
			return false;
		receiverState = false;
		receiver.interrupt();
		receiver = null;
		return true;
	}

	/* DiscoveryListener Methods */
	public void deviceDiscovered(RemoteDevice btDevice, DeviceClass cod) {
		if (!remoteDevices.contains(btDevice))
			remoteDevices.addElement(btDevice);
	}

	public void inquiryCompleted(int discType) {
		synchronized (this) {
			this.notify();
		}
	}

	public void serviceSearchCompleted(int transID, int respCode) {
		transactions++;
		if (transactions == remoteDevicesSize) {
			synchronized (this) {
				this.notify();
			}
		}
	}

	public void servicesDiscovered(int transID, ServiceRecord[] servRecord) {
		synchronized (contactsElement) {
			for (int i = 0; i < servRecord.length; i++) {
				contactsElement[contactsElementSize++] = new MsgContact(
						(String) servRecord[i].getAttributeValue(
								SERVICE_ATTR_ID).getValue(),
						servRecord[i].getConnectionURL(
								ServiceRecord.NOAUTHENTICATE_NOENCRYPT, false),
						(int) servRecord[i].getAttributeValue(SERVICE_ATTR_IMG)
								.getLong());
			}
		}
	}

	public String getAddress() {
		return btAddress;
	}

	public void cleanup() {
		stop = true;
		contactsElement = null;
		contactsElementSize = 0;
		send = null;
		receive = null;
		sender = null;
		receiver = null;
		remoteDevices = null;
		remoteDevicesSize = 0;
		input = null;
		output = null;
		connection = null;
		notifier = null;
		record = null;
	}

	// Runnable
	private final class Sender implements Runnable {
		private String url, msg;

		public void set(String _url, String _msg) {
			url = _url;
			msg = _msg;
		}

		public void run() {
			try {
				connection = (StreamConnection) Connector.open(url);
				output = connection.openDataOutputStream();
				output.writeUTF(msg);
				output.flush();
				output.close();
			} catch (BluetoothStateException e) {
				// TODO review exceptions
			} catch (IOException e) {
				// TODO review exceptions
			}
		}
	}

	private final class Receiver implements Runnable {
		private MessageListener listener;

		public void set(MessageListener _listener) {
			listener = _listener;
		}

		public void run() {
			try {
				notifier = (StreamConnectionNotifier) Connector.open(serviceUrl
						.toString());
				record = localDevice.getRecord(notifier);
				if (btAddress == null)
					btAddress = record.getConnectionURL(
							ServiceRecord.NOAUTHENTICATE_NOENCRYPT, false);
				record.setAttributeValue(SERVICE_ATTR_ID, new DataElement(
						DataElement.STRING, serviceName));
				record.setAttributeValue(SERVICE_ATTR_IMG, new DataElement(
						DataElement.INT_1, image));
			} catch (IOException e) {
				// TODO review exceptions
			}
			while (!stop) {
				try {
					connection = notifier.acceptAndOpen();
					if (connection != null) {
						input = connection.openDataInputStream();
						listener.recieved(input.readUTF());
						input.close();
						connection.close();
					}
				} catch (IOException e) {
					continue;
				}
			}
		}
	}
}